﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Numerics;
using System.Diagnostics;
using System.IO;

namespace BitcoinLibrary
{
    /**
     * A TransactionOutput message contains a scriptPubKey that controls who is able to spend its value. It is a sub-part
     * of the Transaction message.
     */
    public class TransactionOutput : Message /*implements Serializable */
    {
        //private static final Logger log = LoggerFactory.getLogger(TransactionOutput.class);
        private const long serialVersionUID = -590332479859256824L;

        // A transaction output has some value and a script used for authenticating that the redeemer is allowed to spend
        // this output.
        private BigInteger value;
        private byte[] scriptBytes;

        // The script bytes are parsed and turned into a Script on demand.
        private /*transient */ Script scriptPubKey;

        // These fields are Java serialized but not BitCoin serialized. They are used for tracking purposes in our wallet
        // only. If set to true, this output is counted towards our balance. If false and spentBy is null the tx output
        // was owned by us and was sent to somebody else. If false and spentBy is true it means this output was owned by
        // us and used in one of our own transactions (eg, because it is a change output).
        private bool availableForSpending;
        private TransactionInput spentBy;

        // A reference to the transaction which holds this output.
        public Transaction parentTransaction;

        /** Deserializes a transaction output message. This is usually part of a transaction message. */
        public TransactionOutput(NetworkParameters param, Transaction parent, byte[] payload,
                                 int offset)
            : base(param, payload, offset)
        {
            parentTransaction = parent;
            availableForSpending = true;
        }

        public TransactionOutput(NetworkParameters param, Transaction parent, BigInteger value, Address to)
            : base(param)
        {
            this.value = value;
            this.scriptBytes = Script.createOutputScript(to);
            parentTransaction = parent;
            availableForSpending = true;
        }

        /** Used only in creation of the genesis blocks and in unit tests. */
        public TransactionOutput(NetworkParameters param, byte[] scriptBytes)
            : base(param)
        {
            this.scriptBytes = scriptBytes;
            this.value = Utils.toNanoCoins(50, 0);
            availableForSpending = true;
        }

        public Script getScriptPubKey()
        {
            if (scriptPubKey == null)
                scriptPubKey = new Script(param, scriptBytes, 0, scriptBytes.Length);
            return scriptPubKey;
        }

        public override void parse()
        {
            value = readUint64();
            int scriptLen = (int)readVarInt();
            scriptBytes = readBytes(scriptLen);
        }

        public override void bitcoinSerializeToStream(Stream stream)
        {
            Debug.Assert(scriptBytes != null);
            Utils.uint64ToByteStreamLE(getValue(), stream);
            // TODO: Move script serialization into the Script class, where it belongs.
            var data = new VarInt(scriptBytes.Length).encode();
            stream.Write(data, 0, data.Length);
            stream.Write(scriptBytes, 0, scriptBytes.Length);
        }

        /**
         * Returns the value of this output in nanocoins. This is the amount of currency that the destination address
         * receives.
         */
        public BigInteger getValue()
        {
            return value;
        }

        public int getIndex()
        {
            Debug.Assert(parentTransaction != null);
            for (int i = 0; i < parentTransaction.outputs.Count; i++)
            {
                if (parentTransaction.outputs[i] == this)
                    return i;
            }
            // Should never happen.
            throw new Exception("Output linked to wrong parent transaction?");
        }

        /**
         * Sets this objects availableToSpend flag to false and the spentBy pointer to the given input.
         * If the input is null, it means this output was signed over to somebody else rather than one of our own keys.
         */
        internal void markAsSpent(TransactionInput input)
        {
            Debug.Assert(availableForSpending);
            availableForSpending = false;
            spentBy = input;
        }

        internal void markAsUnspent()
        {
            availableForSpending = true;
            spentBy = null;
        }

        internal bool isAvailableForSpending()
        {
            return availableForSpending;
        }

        public byte[] getScriptBytes()
        {
            return scriptBytes;
        }

        /** Returns true if this output is to an address we have the keys for in the wallet. */
        public bool isMine(Wallet wallet)
        {
            try
            {
                byte[] pubkeyHash = getScriptPubKey().getPubKeyHash();
                return wallet.isPubKeyHashMine(pubkeyHash);
            }
            catch (ScriptException e)
            {
                // TODO: log.error("Could not parse tx output script: {}", e.toString());
                return false;
            }
        }

        /** Returns a human readable debug string. */
        public override string ToString()
        {
            try
            {
                return "TxOut of " + Utils.bitcoinValueToFriendlyString(value) + " to " + getScriptPubKey().getToAddress()
                        .ToString() + " script:" + getScriptPubKey().ToString();
            }
            catch (ScriptException e)
            {
                throw e;
            }
        }

        /** Returns the connected input. */
        public TransactionInput getSpentBy()
        {
            return spentBy;
        }
    }
}
